﻿#include <QMetaMethod>
#include <QJsonDocument>
#include <QJsonObject>
#include "JZWebServer.h"
#include "JZCo.h"

JZWebServer::JZWebServer(QObject* parent)
    :QObject(parent)
{
    m_server = new QTcpServer(this);
    connect(m_server, &QTcpServer::newConnection, this, &JZWebServer::onNewConnection);
}

JZWebServer::~JZWebServer()
{
    stop();
}

bool JZWebServer::start(int port)
{
    return m_server->listen(QHostAddress::Any, port);
}

void JZWebServer::stop()
{
    m_server->close();
}

void JZWebServer::onNewConnection()
{
    //获取新连接
    QTcpSocket* socket = m_server->nextPendingConnection();
    if (!socket)
        return;

    m_requests[socket] = JZWebRequestPtr(new HttpRequest());
    jzco_spawn([this, socket] {
        this->processClient(socket);
    });
}

void JZWebServer::processClient(QTcpSocket *socket)
{
    JZCoTcpSocket co_socket(socket);

    auto currentRequest = m_requests[socket].data();
    while (true)
    {
        if (!co_socket.waitForReadyRead(30 * 1000))
        {
            qWarning() << "processClient timeout.";
            break;
        }

        // Collect data for the request object
        while (socket->bytesAvailable() &&
            currentRequest->getStatus() != HttpRequest::complete &&
            currentRequest->getStatus() != HttpRequest::abort_size &&
            currentRequest->getStatus() != HttpRequest::abort_broken)
        {
            currentRequest->readFromSocket(socket);
        }

        int status = currentRequest->getStatus();
        // If the request is aborted, return error message and close the connection
        if (currentRequest->getStatus() == HttpRequest::abort_size)
        {
            socket->write("HTTP/1.1 413 entity too large\r\nConnection: close\r\n\r\n413 Entity too large\r\n");
            break;
        }
        // another reson to abort the request
        else if (currentRequest->getStatus() == HttpRequest::abort_broken)
        {
            socket->write("HTTP/1.1 400 bad request\r\nConnection: close\r\n\r\n400 Bad request\r\n");
            break;
        }
        // If the request is complete, let the request mapper dispatch it
        else if (currentRequest->getStatus() == HttpRequest::complete)
        {
            HttpResponse response;
            onHttpRequest(*currentRequest, response);
            socket->write(response.data());
            break;
        }
    }
    
    jzco_await co_socket.disconnectFromHost();
    m_requests.remove(socket);
}

void JZWebServer::addPage(QString url, JZWebPage* page)
{
    Q_ASSERT(!page->parent() || page->parent() == this);

    if(!page->parent())
        page->setParent(this);
	
    m_pages[url] = page;
}

void JZWebServer::onHttpRequest(const HttpRequest& req, HttpResponse& res)
{
    QString url = req.getPath();
    if (url.endsWith("/"))
        url += "index.html";
    if (url.startsWith("/"))
        url = url.mid(1);

    if (!m_pages.contains(url))
    {
        res.setStatus(404, "not found");
        res.write("404 not found", true);
        return;
    }

    auto page = m_pages[url];
    if (!req.hasParameter("command"))
    {
        QString text = page->render();
        res.setHeader("Content-Type", "text/html");
        res.write(text.toUtf8());
    }
    else
    {
        QJsonParseError json_error;
        QJsonDocument doc = QJsonDocument::fromJson(req.getBody(), &json_error);
        if (json_error.error != QJsonParseError::NoError)
        {
            res.setStatus(400, "Bad Request");

            QString error = "400 Bad Request, " + json_error.errorString();
            res.write(error.toUtf8(), true);
            return;
        }

        QJsonObject req_obj = doc.object();
        int context_id = 0;
        if (!m_contexts.contains(context_id))
        {
            JZWebContextPtr context = JZWebContextPtr(new JZWebContext());
            *context = *page->baseContext();
            m_contexts[context_id] = context;
        }
        JZWebContext* context = m_contexts[context_id].data();
        page->setCurrentContext(context);
        QJsonObject res_obj = page->onCommand(req_obj);
        page->setCurrentContext(nullptr);

        QByteArray res_json = QJsonDocument(res_obj).toJson();
        res.setHeader("Content-Type", "application/json");
        res.write(res_json, true);
    }    
}